package com.brotherly.util;

import java.io.File;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

import android.os.Environment;

import com.brotherly.framework.http.HttpListener;
import com.brotherly.framework.http.HttpService;
import com.brotherly.framework.storage.SimpleStorage;
import com.brotherly.framework.storage.SimpleStorageService;
import com.brotherly.geo.MapHelper;
import com.brotherly.util.downloader.DownLoaderUtil;

public class InternetResourceCache
{
    protected static class CacheItem
    {
        String cache;
        long created_time;

        long last_accessed_time;
        String url;
    }

    private static final String CACHE_NAME = "BrotherlyMPGP/Cache/";
    private static InternetResourceCache mInstance;
    private static final String OPTION_CACHE_DATA = "cache_data";
    private static final String OPTION_CACHE_SIZE = "cache_size";
    private static final String STORAGE = "com.brotherly.framework.around";

    public static String getCacheDir()
    {
        String root = Environment.getExternalStorageDirectory().getPath();
        String cacheDir = String.format("%s/%s", root, CACHE_NAME);
        File dir = new File(cacheDir);
        if (!dir.exists())
        {
            dir.mkdirs();
        }
        return cacheDir;
    }

    public static boolean delCacheDir()
    {
        return FileSystem.deleteDirectory(DownLoaderUtil.APK_PATH);
    }

    public static InternetResourceCache getInstance()
    {
        if (mInstance == null)
        {
            mInstance = new InternetResourceCache();
        }

        return mInstance;
    }

    private static boolean isExternalStorage()
    {
        return (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED));
    }

    protected Map<String, CacheItem> caches = new HashMap<String, CacheItem>();
    protected int cacheSize = 100;
    protected SimpleStorageService storageServ;

    public synchronized void removeCachedData(String url)
    {
        caches.remove(url);
        saveCache();
    }

    protected InternetResourceCache()
    {}

    public boolean cacheHttpResource(final String url, String method, Map<String, Object> headers, byte[] body,
            final HttpListener listener,
            boolean wait, HttpService httpServ)
    {
        if (getCacheFilename(url) != null)
            return false;

        HttpListener cacheListener = new HttpListener()
        {
            @Override
            public void result(int code, Map<String, String> headers, byte[] body)
            {
                if (code / 100 == 2)
                {
                    cacheIt(url, body);
                }

                if (listener != null)
                {
                    try
                    {
                        listener.result(code, headers, body);
                    }
                    catch (Exception e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        };

        try
        {
            httpServ.request(url, method, headers, body, cacheListener, wait);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

        return true;
    }

    public synchronized void cacheIt(String url, byte[] data)
    {
        CacheItem item = new CacheItem();

        item.url = url;

        item.cache = getCacheDir() + "/" + UUID.randomUUID().toString();

        item.created_time = System.currentTimeMillis();
        item.last_accessed_time = item.created_time;

        FileSystem.writeBytes(new File(item.cache), data);

        caches.put(item.url, item);

        saveCache();
    }

    protected String flushCacheData()
    {
        StringWriter writer = new StringWriter();

        JSonSax sax = new JSonSax(writer);

        sax.startArray();

        for (CacheItem item : caches.values())
        {
            sax.startMap();
            sax.writeString("url");
            sax.writeString(item.url);
            sax.writeString("cache");
            sax.writeString(item.cache);
            sax.writeString("created_time");
            sax.writeLong(item.created_time);
            sax.writeString("last_accessed_time");
            sax.writeLong(item.last_accessed_time);
            sax.endMap();
        }
        sax.endArray();
        return writer.toString();
    }

    public synchronized String getCacheFilename(String url)
    {
        CacheItem item = caches.get(url);
        if (item == null)
            return null;
        item.last_accessed_time = System.currentTimeMillis();
        return item.cache;
    }

    public int getCacheSize()
    {
        return cacheSize;
    }

    public void loadCache(SimpleStorageService storageServ)
    {
        this.storageServ = storageServ;
        SimpleStorage storage = storageServ.openStorage(STORAGE);
        String strCacheSize = storage.getWithDefault(OPTION_CACHE_SIZE, "100");
        setCacheSize(Integer.parseInt(strCacheSize));
        String strCacheData = storage.getWithDefault(OPTION_CACHE_DATA, "[]");
        parseCacheData(strCacheData);
        storage.close();
    }

    protected void parseCacheData(String data)
    {
        JSONArray array = null;

        try
        {
            array = (JSONArray) (new JSONParser()).parse(data);
        }
        catch (Exception e)
        {}

        if (array == null)
            return;

        for (int index = 0; index != array.size(); ++index)
        {
            JSONObject item = (JSONObject) array.get(index);

            CacheItem cacheItem = new CacheItem();

            cacheItem.url = MapHelper.getString(item, "url");
            cacheItem.cache = MapHelper.getString(item, "cache");

            cacheItem.created_time = MapHelper.getNumber(item, "created_time").longValue();
            cacheItem.last_accessed_time = MapHelper.getNumber(item, "last_accessed_time").longValue();

            caches.put(cacheItem.url, cacheItem);
        }
    }

    public synchronized void removeOldestItem(int size)
    {
        ArrayList<CacheItem> items = new ArrayList<CacheItem>();
        items.addAll(caches.values());

        Collections.sort(items, new Comparator<CacheItem>()
        {
            @Override
            public int compare(CacheItem left, CacheItem right)
            {
                return (int) (left.last_accessed_time - right.last_accessed_time);
            }
        });

        for (int index = 0; index != size; ++index)
        {
            CacheItem item = items.get(index);

            caches.remove(item.url);
        }
    }

    public synchronized void saveCache()
    {
        int size = cacheSize * (isExternalStorage() ? 100 : 1);

        if (caches.size() > size)
        {
            removeOldestItem(caches.size() - size);
        }

        try
        {
            SimpleStorage storage = storageServ.openStorage(STORAGE);
            storage.put(OPTION_CACHE_DATA, flushCacheData());
            storage.commit();
            storage.close();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    public void setCacheSize(int cacheSize)
    {
        this.cacheSize = cacheSize;
    }
}
